iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
Software Development

Rails Active Model系列 第 27

D-27 合理規劃 validation 規則

  • 分享至 

  • xImage
  •  

規劃 life cycle的章節,筆者提到過可以在物件執行階段加上 run_callbacks 來在您想要的地方運作指定的 callbacks,但示例的前前後後好像都沒有寫到 validation 的階段耶?他跑到哪裡去了?

還記不記得筆者在這篇提過 ActiveModel::Validations::Callbacks 這個 module ?

在使用它之後,就可以加上像是

before_validation
after_validation

的 callbacks 囉!

這些 callbacks 加上 Rails 內建的驗證規則,能夠讓你的 form object 很簡單就能做到防止使用者操作失誤的防呆功用,以及權限的管理等等,而這也是驗證機制的主要功能之一 - 防止意外操作。

然而,一旦要做到權限管理,我想有的人可能會這樣做:

class ArticleForm
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::Validations::Callbacks

  attr_accessor :current_user
  # current_user 在 controller 階段帶入,為了不被 attribute_types 自動抓出來 render 欄位,改用 attr_accessor

  attribute :title, :string, default: ''
  attribute :content, :string, default: ''

  before_validation :check_permission

  validates_length_of :title, minimun: 5
  validates_length_of :content, minimun: 20
  # ... 一些 validation 與其他定義

  def check_permission
    # .. 對傳進來的 current_user 做權限檢查
    if current_user.role < :admin # 像這種寫法需要搭配 comparable 來使用,之後會再提到
      raise "insufficient permission." # 如果權限不夠就拋出錯誤
    end
  end
end

當然,這樣的模式在正式環境下運作是沒什麼問題的,但要是有額外的客制需求,需要進 Rails console 直接拿這個 form object 來操作,請問要去哪裡生出一個 current_user?如果有建立 User model 還好說,從 DB query 出來放進去就是了,那萬一是存在 session 的資料呢?

或者,如果你的 form object 還規劃了像是 after_execute :send_notification 這種動作後寄出通知信給使用者的動作,但我在 Rails console 裡面跑 script 改資料不想寄出通知信呀!那是不是就只能暫時的 monket patch 把 send_notification 給覆蓋掉?無形中增加維運的成本。

在規劃不論是 model 或是 service object 的時候永遠要記得考慮這點,那就是要維持這東西就算在 Rails console 也能很好用的程度。

你做的東西,除了用來服務使用者之外,同時也要能夠兼顧到使用彈性。
儘管筆者介紹的這一系列不管是 callbacks, validation 等等都很好用,但也不能就一股腦的把邏輯通通組合在一起。
像上面這個範例,筆者就會把權限判斷拆到 controller 層去做判斷:

class ArticleController
  before_action :check_permission, only: :create # 在 controller 層去判斷權限
  after_action :send_notification, only: :create # 寄送通知也放在 controller 層
  def create
    @form = ArticleForm.new(article_params)
    if @form.valid?
      @form.execute
      redirect_to article_path @form.article
    else
      render :new
    end
  end
end

這樣一來,這個 form object 就只需要負責對傳送進去的參數做驗證,也不會做多餘的事情,皆大歡喜!


上一篇
D-26 如何把 run_callbacks 藏起來?
下一篇
D-28 自定比較規則 - Comparable
系列文
Rails Active Model28
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言